#ifndef Audio_h__
#define Audio_h__

#include "Vec3.h"
#include "sndfile.h"
#include "portaudio.h"
#include <LuaPlus.h>
#include <vector>
#include <map>
#include <set>

class DirectXMesh;
class BPhysicsActor;

namespace Audio_Engine
{    
#define NUM_CHAN 2
#define LEFT_CHAN 0
#define RIGHT_CHAN 1

    typedef unsigned int audio_id;

    namespace CAST_ID
    {
        typedef enum
        {
            NOT_SET,
            WAV_PLAYER,
            DELAY_NODE,
            POSITON,
            ECHO
        } cast_id;
    }

    class Audio_Source
    {
    public:
        float* m_buffer;
        unsigned long m_count;
        std::string m_name;
    };

    //currently, implemented by CameraListener class
    class Audio_Listener
    {
    public:
        virtual ~Audio_Listener() = 0 {}
        virtual const Vec3& vGetPosition() const = 0;
        virtual const Vec3& vGetDirection() const = 0;
        virtual const Vec3& vGetUp() const =0;
        virtual const float vGetRadiusSquared() const = 0;
    };

    //Frwrd Dcl's
    class AudioNodeI;
    class Geometry_Manager;
    class Audio_Geometry;
    class Audio_Collision_Shape;
    class Audio_Sphere_Shape;

    typedef std::map<std::string,Audio_Source*> mSources;
    typedef std::vector<AudioNodeI*> vNodes;

    class AudioManager
    {
#pragma region Singleton Implementation
    private:
        AudioManager() : 
           m_framesPerBuffer(0),
               m_channels(0),
               m_sampleRate(0),
               m_lChanAccum(NULL),
               m_rChanAccum(NULL),
               m_stream(NULL),
               m_listener(NULL),
               m_active(false),
               m_copyBuffer(NULL),
               m_maxSounds(0),
               m_idGenerator(0){}
           AudioManager(const AudioManager& other){}
           ~AudioManager(){}
           AudioManager& operator=(const AudioManager* other){}
    public:
        static AudioManager* Instance()
        {
            static AudioManager* g = NULL;
            if( g == NULL)
            {
                g = new AudioManager;
            }
            return g;
        }
        static void Kill() 
        {
            delete Instance();
        }
#pragma endregion
    public:

        /************************************************************************/
        /* LUA                                                                  */
        /************************************************************************/
    public:
        static LuaPlus::LuaObject* StaticInitializeLuaMetaTable();
        static LuaPlus::LuaObject* StaticGetLuaMetaTable(){return Static_MetaTable;}
    private:
        static LuaPlus::LuaObject* Static_MetaTable;
        /************************************************************************/
        /* Sound Engine                                                         */
        /************************************************************************/
    public:
        void LInitialiseEngine(int framesPerBuffer, int channels,
            int sampleRate, float maxDeleySeconds, int maxSounds);
        void LStopAll();
        void SetListener(Audio_Listener* listener){m_listener = listener;}
        void Update();
        void ShutDown();
        void LStopNode(audio_id samp);
        void StopNode(audio_id samp);
        void StopSample(int samp);
        void LPauseAudio();
        void PauseAudio();
        void LStartAudio();
        void StartAudio();
        float CalculateTimeToListener(const Vec3& start);
        void LCreateSource( LuaPlus::LuaObject obj);
        audio_id CreateDelayNode( audio_id which, float secs );
        audio_id LCreateDelayNode( audio_id which, float secs );
        audio_id LCreatePositionNode( audio_id which, float radius, bool stopSource, float x, float y, float z);
        audio_id CreatePositionNode( audio_id which , float radius, bool stopSource, float x, float y, float z);
        audio_id Create3DSound(const std::string& sourceName, Vec3 position, float radius);
        audio_id LCreate3DSound(const char* name, float x, float y, float z, float radius);
        Audio_Source* GetSource(const std::string& name);
        void CreateSource(  const char* path, const char* name, 
            float volume/*0 to 1*/ );
        audio_id CreateWAVNode(const std::string& sourceName,  bool loop, float fadeIn, float fadeOut);
        audio_id LCreateWAVNode(const char* sourceName, bool loop, float fadeIn, float fadeOut);
        Geometry_Manager* GetGeometryManager() {return m_geomManager;}
        AudioNodeI* GetNode( audio_id which );
        bool IsNodeInActive(audio_id id);

        //Entry Points
        static int StaticEntryPoint(
            const void *input, void *output,
            unsigned long frameCount,
            const PaStreamCallbackTimeInfo* timeInfo,
            PaStreamCallbackFlags statusFlags,
            void *userData );
        int PaStreamCallback( 
            const void *input, void *output, 
            unsigned long frameCount, 
            const PaStreamCallbackTimeInfo* timeInfo, 
            PaStreamCallbackFlags statusFlags, 
            void *userData );

        unsigned int GenerateNewID(){return m_idGenerator++;}
        int GetFramesPerBuffer(){return m_framesPerBuffer;}
        int GetSampleRate(){return m_sampleRate;}

    private:
        bool m_active;
        int m_framesPerBuffer;
        int m_channels;
        int m_sampleRate;
        void* m_stream;
        Audio_Listener* m_listener;  //the one and only!
        float m_maxDeleyTime;       //seconds
        int m_maxSounds;
        unsigned int m_idGenerator;
        Geometry_Manager* m_geomManager;
    public:
        Audio_Listener* GetListener(){return m_listener;}
        mSources m_sources;         
        vNodes   m_waitingNodes;      
        vNodes   m_activeNodes;      
        vNodes   m_allNodes;    
        float* m_lChanAccum;      
        float* m_rChanAccum;   
        float* m_copyBuffer;
        float m_currentVolumeValue;
        float m_targetVolumeValue;
    };

    template <class T> 
    class Safe_Caster
    {
    public:
        T* SafeCast(AudioNodeI* target)
        {
            if(target->m_cast_id == T::sCastID)
            {
                T* ret = dynamic_cast<T*>(target);
                return ret;
            }
            return NULL;
        }
    };

    class AudioNodeI
    {
    public:
        AudioNodeI(
            int framesPB, audio_id id,
            CAST_ID::cast_id cid = CAST_ID::NOT_SET) : 
        m_cast_id(cid) , m_audio_id(id) 
        {
            m_bInbus[LEFT_CHAN] = new float[framesPB];
            m_bInbus[RIGHT_CHAN] = new float[framesPB];
            m_bOutbus[LEFT_CHAN] = new float[framesPB];
            m_bOutbus[RIGHT_CHAN] = new float[framesPB];
            m_bLeftOut = new float[framesPB];
            m_bRightOut = new float[framesPB];
            memset(m_bInbus[LEFT_CHAN], 0, sizeof(float) * framesPB);
            memset(m_bOutbus[LEFT_CHAN], 0, sizeof(float) * framesPB);
            memset(m_bInbus[RIGHT_CHAN], 0, sizeof(float) * framesPB);
            memset(m_bOutbus[RIGHT_CHAN], 0, sizeof(float) * framesPB);
            memset(m_bLeftOut, 0, sizeof(float) * framesPB);
            memset(m_bRightOut, 0, sizeof(float) * framesPB);
        }
        virtual ~AudioNodeI() = 0 
        {
            if (m_bInbus[LEFT_CHAN])
            { delete[] m_bInbus[LEFT_CHAN]; }
            if (m_bOutbus[LEFT_CHAN])
            { delete[] m_bOutbus[LEFT_CHAN]; }
            if (m_bInbus[RIGHT_CHAN])
            { delete[] m_bInbus[RIGHT_CHAN]; }
            if (m_bOutbus[RIGHT_CHAN])
            { delete[] m_bOutbus[RIGHT_CHAN]; }
            if (m_bLeftOut)
            { delete[] m_bLeftOut; }
            if (m_bRightOut)
            { delete[] m_bRightOut; }
        }
        virtual void Terminate() 
        { 
            for(unsigned int i = 0; i < m_notificatons.size(); ++i)
                (*m_notificatons[i])++;

            m_notificatons.clear();
        }
        virtual void vInternalUpdate(int frames) = 0;
        virtual void vExternalUpdate() = 0;
        float* GetInbus(int i){return m_bInbus[i];}
        float* GetOutBus(int i){return m_bOutbus[i];}
        void AddNotification(int* notifier){m_notificatons.push_back(notifier);}
        void FlushNotifications() {m_notificatons.clear();}
        void ForceNotifications() 
        {
            for(unsigned int i = 0; i < m_notificatons.size(); ++i)
                (*m_notificatons[i])++;
            m_notificatons.clear();
        }
        void OutPutZero(int bufSize)
        {
            memset(m_bLeftOut, 0, sizeof(float) * bufSize);
            memset(m_bRightOut, 0, sizeof(float) * bufSize);
        }
        void SetUpLoopBack()
        {
            memset(m_bInbus[LEFT_CHAN], 1, sizeof(float) *  AudioManager::Instance()->GetFramesPerBuffer());
            memset(m_bInbus[RIGHT_CHAN], 1, sizeof(float) *  AudioManager::Instance()->GetFramesPerBuffer());
            //make the calculated output affect itself
            m_bInput[LEFT_CHAN] = m_bInbus[LEFT_CHAN];       // NOT USED
            m_bInput[RIGHT_CHAN] = m_bInbus[RIGHT_CHAN];       // NOT USED
            m_bOutput[LEFT_CHAN] = m_bOutbus[LEFT_CHAN];
            m_bOutput[RIGHT_CHAN] = m_bOutbus[RIGHT_CHAN];
        }
        float* m_bLeftOut;
        float* m_bRightOut;
        bool m_active;
        audio_id m_audio_id;
        CAST_ID::cast_id m_cast_id;
    protected:
        float* m_bOutput[NUM_CHAN];
        float* m_bInput[NUM_CHAN];

        float* m_bInbus[NUM_CHAN];
        float* m_bOutbus[NUM_CHAN];

        std::vector<int*> m_notificatons;
    };
    
    class Wav_Player : public AudioNodeI
    {
    public:
        Wav_Player() :
          AudioNodeI( AudioManager::Instance()->GetFramesPerBuffer(), 
              AudioManager::Instance()->GenerateNewID(), CAST_ID::WAV_PLAYER), 
              m_index(0),
              m_amplitude(0.0f),
              m_loop(false), m_outPut(true) {}
          ~Wav_Player()
          {
              int test = 0;
          }
          virtual void vInternalUpdate(int frames);
          virtual void vExternalUpdate();
          void SetSource(Audio_Source* source){m_source = source;}
          void SetAmplitude(float amp){m_amplitude = amp;}
          void SetLoop(bool shouldLoop){m_loop = shouldLoop;}
          void StopOutput();
          void SetFades(float inF, float outF)
          {
              m_fadeIN = inF * AudioManager::Instance()->GetSampleRate();
              m_fadeOUT = outF * AudioManager::Instance()->GetSampleRate();
          }
          float* GetAmplitude(){return &m_amplitude;}
          static CAST_ID::cast_id sCastID;
    private:
        //read from the beggining of this buffer
        Audio_Source* m_source;
        //pluss this amount of floats
        unsigned int m_index;
        //extra's
        float m_amplitude;
        float m_fadeIN;
        float m_fadeOUT;
        bool m_loop;
        bool m_outPut;
    };

    class Delay_Node : public AudioNodeI
    {
    public:
        Delay_Node() : 
          AudioNodeI(AudioManager::Instance()->GetFramesPerBuffer(), 
              AudioManager::Instance()->GenerateNewID(),CAST_ID::DELAY_NODE),
              m_delayRead(0),
              m_delayWrite(0),
              m_actualDelay(0),
              m_delayNotification(0),
              m_targetDelay(0),
              m_source(NULL)
          {
              m_delayBuffer[LEFT_CHAN] = m_delayBuffer[RIGHT_CHAN] = NULL;
          }
          virtual ~Delay_Node() 
          {
              if(m_delayBuffer[LEFT_CHAN])
                  delete[] m_delayBuffer[LEFT_CHAN];
              if(m_delayBuffer[RIGHT_CHAN])
                  delete[] m_delayBuffer[RIGHT_CHAN];
          }
          virtual void Terminate()
          {
              if(m_source)
              {
                  m_source->FlushNotifications();
                  m_source = NULL;
              }
              AudioNodeI::Terminate();
          }
          virtual void vInternalUpdate(int frames);
          virtual void vExternalUpdate();
          void CreateDelay(float seconds, AudioNodeI* target);
          void UpdateDelayValue(int newDelay){m_targetDelay = newDelay;}
          int GetActualDelay(){return m_actualDelay;}
          static int sDelayMax;
          static CAST_ID::cast_id sCastID;
    private:
        float* m_delayBuffer[NUM_CHAN];
         AudioNodeI* m_source;
        int m_delayRead;
        int m_delayWrite;
        int m_actualDelay;
        int m_delayNotification;
        int m_targetDelay;
    };

    class Position_Node : public AudioNodeI
    {
    public:
        Position_Node() : 
          AudioNodeI(AudioManager::Instance()->GetFramesPerBuffer(), 
              AudioManager::Instance()->GenerateNewID(),CAST_ID::POSITON),
              m_radiusSquared(0.0f),
              m_sourceNotifier(0),
              m_distanceAtten(0.0f),
              m_shape(NULL)
          { m_pan[LEFT_CHAN] = m_pan [RIGHT_CHAN] = 0.0f;}
          ~Position_Node();
          virtual void vInternalUpdate(int frames);
          virtual void vExternalUpdate();
          void CreatePosition( AudioNodeI* target );
          void SetRadiusSquared(float rad){m_radiusSquared =rad;}
          const Vec3& GetPosition(){return m_position;}
          float GetRadiusSquared(){return m_radiusSquared;}
          void SetPosition(const Vec3& p){m_position = p;}
          float GetAttenuation(){return m_distanceAtten;}
          void CalculateAABBFromSphere()
          {
              float rad = sqrtf(m_radiusSquared);
              m_aabbMin.SetStats(-rad,-rad,-rad);
              m_aabbMax.SetStats(rad,rad,rad);
          }
          int GetSourceNotifier(){return m_sourceNotifier;}
          void SetAttenuation(float a){m_distanceAtten =a;}
          Audio_Collision_Shape* GetCollisionShape(){return m_shape;}
          static CAST_ID::cast_id sCastID;
    private:
        Audio_Collision_Shape* m_shape;
        Vec3 m_position;
        Vec3 m_aabbMin,m_aabbMax;
        float m_radiusSquared;
        float m_pan[NUM_CHAN];
        float m_distanceAtten;
        int m_sourceNotifier;
    };

    class Echo_Node: public AudioNodeI
    {
    public:
        Echo_Node() : 
          AudioNodeI(AudioManager::Instance()->GetFramesPerBuffer(), 
              AudioManager::Instance()->GenerateNewID(),CAST_ID::ECHO),
              m_source(NULL),
              m_sourceToSelf(NULL),
              m_selfToListenDelay(NULL),
              m_sourceNotifier(0),
              m_pNode(NULL),
              m_selfGeom(NULL),
              m_shape(NULL)
          {}
          ~Echo_Node();
          virtual void vInternalUpdate(int frames);
          virtual void vExternalUpdate();
          void CreateEcho(Wav_Player* sound, Position_Node* source, 
                          Position_Node* myNode, Audio_Collision_Shape* shape);
          static CAST_ID::cast_id sCastID;
          const Vec3& GetPosition(){return m_pos;}
          void SetPosition(const Vec3& pos){m_pos = pos;}
          float GetRadiusSquared()
          {
              if(m_pNode)
              {
                  return m_pNode->GetRadiusSquared();
              } 
              else 
              {return 0.0f;}
          }
          float GetAttenuation()
          {
              if(m_pNode)
              {
                  return m_pNode->GetAttenuation();
              } 
              else 
              {return 0.0f;}
          }
          void SetShape(Audio_Collision_Shape* s){m_shape = s;}
          Audio_Collision_Shape* GetCollisionShape(){return m_shape;}
    private:
        Position_Node* m_source;
        Vec3 m_pos;
        Delay_Node* m_sourceToSelf;
        Delay_Node* m_selfToListenDelay;
        Audio_Geometry* m_selfGeom;
        Position_Node* m_pNode;
        int m_sourceNotifier;
        Audio_Collision_Shape* m_shape;
    };

    class Audio_Geometry; //frwrdDcl
    struct Audio_Triangle
    {
        Vec3 m_verts[3];
        Vec3 m_norm;
    };
    struct Audio_Collision
    {
        Audio_Geometry* which;
        int triIndex;
        Vec3 normal;
    };

    typedef std::vector<Audio_Collision> vAudioCols; //incase any more info needs to be added later
    typedef std::vector<Audio_Triangle> vAudioTris; //Should be some Grid of some kind

    class Audio_Collision_Shape
    {
    public:
        virtual ~Audio_Collision_Shape() = 0 {}
        virtual bool CollidesWithListener(Audio_Listener* l, float* dist) = 0;
        virtual void UpdateTranslation(const Vec3& newTrans) = 0;
        virtual void DebugDrawShape(const Vec3& color) = 0;
    };

    class Audio_Sphere_Shape : public Audio_Collision_Shape
    {
    public:
        Audio_Sphere_Shape() : m_radSquared(0.0f) {}
        virtual bool CollidesWithListener(Audio_Listener* l, float* dist);
        virtual void UpdateTranslation(const Vec3& newTrans){m_pos = newTrans;}
        void SetRadiusSquared(float rad) {m_radSquared = rad;}
        virtual void DebugDrawShape(const Vec3& color);
    private:
        Vec3 m_pos;
        float m_radSquared;
    };

    class Audio_HemiSphere_Shape: public Audio_Collision_Shape
    {
    public:
        virtual bool CollidesWithListener(Audio_Listener* l, float* dist);
        virtual void UpdateTranslation(const Vec3& newTrans){m_position = newTrans;
                           SetUp(m_position, m_plane.GetNormal(), m_radiusSquared);}
        void SetUp( const Vec3& pos, const Vec3& normal, float radSQR);
        virtual void DebugDrawShape(const Vec3& color);
    private:
        Plane m_plane;
        Vec3 m_position;
        float m_radiusSquared;
    };

    class Audio_Geometry
    {
    public:
        Audio_Geometry() : m_radiusSquared(0.0f) {}
        void CreateSphereBVFromMesh(DirectXMesh* mesh, float scale);
        void GenerateGeometryFromActor(BPhysicsActor* actor, float scale );
        const Vec3& GetPosition(){return m_centerPosition;}
        float GetRadiusSquared(){return m_radiusSquared;}
        vAudioTris m_tris;
    private:
       
        Vec3 m_centerPosition;
        float m_radiusSquared;
        Vec3 m_aabbMin, m_aabbMax;
    };

    //Helper classes
    struct Collision_Cache
    {
        float dist;
        Audio_Geometry* which;
    };
    struct Collision_Cache_Compare
    {
        bool operator() (const Collision_Cache& lhs,const Collision_Cache& rhs )
        {
            return lhs.dist < rhs.dist;
        }
    };
    typedef std::set<Collision_Cache, Collision_Cache_Compare> audio_cash_set;
    typedef std::vector<Audio_Geometry*> vAudioGeom; //This should be a BVH

    class Geometry_Manager
    {
    public:
        void CleanUp()
        {
            m_externalGeom.clear();
            for(unsigned int i = 0; i < m_managedGeom.size(); ++i)
                delete m_managedGeom[i];
            m_managedGeom.clear();
        }
        void AddManagedGeometry(Audio_Geometry* geom)
        {
            m_managedGeom.push_back(geom);
        }
        void AddExtermalGeom(Audio_Geometry* geom)
        {
            m_externalGeom.push_back(geom);
        }
        void CheckCollisions(vAudioCols& cols, Position_Node* pN);
        vAudioGeom& GetManagedGeometry() {return m_managedGeom;}
    private:
        vAudioGeom m_managedGeom;
        vAudioGeom m_externalGeom;
    };

}; //Audio Engine namepspace
#endif // Audio_h_
